#ifndef SINGLY_LINKED_LIST_H
#define SINGLY_LINKED_LIST_H

#include <iostream>
#include <stdexcept>
#include "ListNode.h"
using namespace std;

template <typename T>
class SinglyLinkedList {
    friend ostream& operator<<(ostream& out, const SinglyLinkedList<T>& list) {
        Node* curr = list._head;
        out << "[";
        while (curr) {
            out << curr->_value;
            if (curr->_next) {
                out << ", ";
            }
            curr = curr->_next;
        }
        out << "]";
        return out;
    }

public:
    using Node = ListNode<T>;

    SinglyLinkedList() : _head(nullptr), _size(0) {}
    SinglyLinkedList(const SinglyLinkedList& list);
    SinglyLinkedList<T>& operator=(const SinglyLinkedList& list);
    ~SinglyLinkedList();
    [[nodiscard]] int size() const;
    void pushBack(const T& value);
    void pushFront(const T& value);
    void popFront();
    void popBack();
    T& front() const;
    T& back() const;
    T& indexAt(int index) const;
    int find(const T& value) const;
    void addAfter(int index, const T& value);
    void removeAt(int index);
    void clear();

private:
    Node* _head;
    int _size;
};

template<typename T>
SinglyLinkedList<T>::SinglyLinkedList(const SinglyLinkedList &list) : _head(nullptr), _size(0) {
    Node* curr = list._head;
    while (curr) {
        pushBack(curr->_value);
        curr = curr->_next;
    }
}

template<typename T>
SinglyLinkedList<T>& SinglyLinkedList<T>::operator=(const SinglyLinkedList &list) {
    if (this == &list) {
        return *this;
    }
    clear();
    Node* curr = list._head;
    while (curr) {
        pushBack(curr->_value);
        curr = curr->_next;
    }
    return *this;
}

template <typename T>
void SinglyLinkedList<T>::pushFront(const T& value) {
    Node* newNode = new Node(value);
    newNode->_next = _head;
    _head = newNode;
    _size++;
}

template <typename T>
void SinglyLinkedList<T>::pushBack(const T& value) {
    Node* newNode = new Node(value);
    if (_head) {
        Node* curr = _head;
        while (curr->_next) {
            curr = curr->_next;
        }
        curr->_next = newNode;

    } else {
        _head = newNode;
    }
    _size++;
}

template <typename T>
void SinglyLinkedList<T>::popFront() {
    if (_size) {
        Node* temp = _head;
        _head = _head->_next;
        delete temp;
        _size--;
    } else {
        throw std::runtime_error("List is empty");
    }
}

template <typename T>
void SinglyLinkedList<T>::popBack() {
    if (!_head) {
        throw std::runtime_error("List is empty");
    } else {
        Node* curr = _head;
        if (curr->_next == nullptr) {
            delete curr;
            _head = nullptr;
        } else {
            while (curr->_next->_next) {
                curr = curr->_next;
            }
            delete curr->_next;
            curr->_next = nullptr;
        }
        _size--;
    }
}

template <typename T>
int SinglyLinkedList<T>::size() const {
    return _size;
}

template <typename T>
T& SinglyLinkedList<T>::front() const {
    if (_head) {
        return _head->_value;
    } else {
        throw std::runtime_error("List is empty");
    }
}

template <typename T>
T& SinglyLinkedList<T>::back() const {
    if (_head) {
        Node* curr = _head;
        while (curr->_next) {
            curr = curr->_next;
        }
        return curr->_value;
    } else {
        throw std::runtime_error("List is empty");
    }
}

template <typename T>
T& SinglyLinkedList<T>::indexAt(int index) const {
    if (index < 0 || index >= _size) {
        throw std::out_of_range("Index out of range");
    } else {
        Node* curr = _head;
        for (int i = 0; i < index; i++) {
            curr = curr->_next;
        }
        return curr->_value;
    }
}

template <typename T>
void SinglyLinkedList<T>::addAfter(int index, const T &value) {
    if (index < 0 || index >= _size) {
        throw std::out_of_range("Index out of range");
    } else {
        Node* newNode = new Node(value);
        Node* curr = _head;
        for (int i = 0; i < index; i++) {
            curr = curr->_next;
        }
        newNode->_next = curr->_next;
        curr->_next = newNode;
        _size++;
    }
}

template <typename T>
void SinglyLinkedList<T>::removeAt(int index) {
    if (index < 0 || index >= _size) {
        throw std::out_of_range("Index out of range");
    } else {
        if (index == 0) {
            popFront();
        } else {
            Node* curr = _head;
            for (int i = 0; i < index - 1; i++) {
                curr = curr->_next;
            }
            Node* temp = curr->_next;
            curr->_next = curr->_next->_next;
            delete temp;
            _size--;
        }
    }
}

template <typename T>
void SinglyLinkedList<T>::clear() {
    while (_head) {
        popFront();
    }
    if (_size) {
        throw std::runtime_error("Error size");
    }
}

template <typename T>
SinglyLinkedList<T>::~SinglyLinkedList() {
    clear();
    delete _head;
}

template <typename T>
int SinglyLinkedList<T>::find(const T& value) const {
    if (_head) {
        Node* curr = _head;
        int ret = 0;
        while (curr) {
            if (curr->_value == value) {
                return ret;
            } else {
                ret++;
            }
            curr = curr->_next;
        }
        return -10086;
    }
    return -10086;
}

#endif
